﻿using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace MassiveBattle
{
	public static class Utility
	{
		public static int SelectRandomIndex(params float[] weightList)
		{
			return SelectRandomIndex(weightList as IList<float>);
		}

		public static int SelectRandomIndex(IList<float> weightList)
		{
			float weightTotal = 0f;

			foreach (var weight in weightList)
				weightTotal += weight;

			float probability = UnityEngine.Random.value * weightTotal;

			weightTotal = 0f;

			for (int i = 0; i < weightList.Count; i++)
			{
				weightTotal += weightList[i];

				if (probability <= weightTotal)
					return i;
			}

			return -1;
		}

		public static float GetInterpolationParameter(float tCurrent, float tBefore)
		{
			if (tCurrent == 0f)
				return 0f;

			if (tBefore == 0f)
				return tCurrent;

			return (tCurrent - tBefore) / (1f - tBefore);
		}

		public static IEnumerable<T> GetEnumValues<T>()
		{
			return Enum.GetValues(typeof(T)).OfType<T>();
		}

		public static IEnumerable<T> GetEnumValues<T>(params T[] exceptList)
		{
			return Enum.GetValues(typeof(T)).OfType<T>().Except(exceptList);
		}

		public static void GetModelInfo(int modelId, out string className, out string gender)
		{
			var modelName = GameData.Inst.ModelDefineDic[modelId];

			GetModelInfo(modelName, out className, out gender);
		}

		public static void GetModelInfo(string modelName, out string className, out string gender)
		{
			var wordList = modelName.Split('_');
			var wordCount = Array.FindIndex(wordList, word => string.Equals(word, "M") || string.Equals(word, "W"));

			className = string.Join("_", wordList, 0, wordCount == -1 ? wordList.Length : wordCount);
			gender = wordCount == -1 ? null : wordList[wordCount];
		}

		/// <summary>
		/// 피해량 획득.
		/// </summary>
		/// <param name="source"></param>
		/// <param name="target"></param>
		/// <returns></returns>
		public static int GetDamagePoint(Manager.BaseManager manager, Controller.BaseStat source, Controller.BaseStat target)
		{
			if (manager.cheat.isDamagePlayer)
			{
				if (manager.DataList[ESide.PLAYER].CharacterList.Exists(data => data.Stat == source))
					return UnityEngine.Random.Range((int)manager.cheat.damagePlayerMin, (int)manager.cheat.damagePlayerMax);
			}

			if (manager.cheat.isDamageEnemy)
			{
				if (manager.DataList[ESide.ENEMY].CharacterList.Exists(data => data.Stat == source))
					return UnityEngine.Random.Range((int)manager.cheat.damageEnemyMin, (int)manager.cheat.damageEnemyMax);
			}

			var monitor = manager.Monitor;
			// 감소 기준값(d) = 6
			// 데미지 감소율 = 1 – (방어도 + 타입 방어도) / (방어도 + 타입방어도 + d * 공격자 레벨)
			// 최대 데미지 감소는 0.7
			// 실제 피해 = 공격자 공격력 * 데미지 감소율
			float a = target.baseDef.Get(monitor);

			switch (source.atkType.Get(monitor))
			{
				case 1: a += target.melee.Get(monitor); break;
				case 2: a += target.range.Get(monitor); break;
				case 3: a += target.magic.Get(monitor); break;
				case 4: a += target.pulse.Get(monitor); break;
				case 5: a += target.special.Get(monitor); break;
			}

			float b = 1 - a / (a + 6f * source.Level);

			return Mathf.FloorToInt(source.baseAtk.Get(monitor) * Mathf.Clamp(b, 0f, 0.7f));
		}

		/// <summary>
		/// 회복량 획득.
		/// </summary>
		/// <param name="source"></param>
		/// <param name="target"></param>
		/// <returns></returns>
		public static int GetHealPoint(Manager.BaseManager manager, Controller.BaseStat source, Controller.BaseStat target)
		{
			var monitor = manager.Monitor;

			// 치유량 = 치유 클래스의 공격력 / 8 , (단 한번에 치유되는 생명력은 대상의 최대 생명력의 1/3)
			return Mathf.FloorToInt(Mathf.Clamp(source.baseAtk.Get(monitor) * 0.125f, 0, target.baseHP.Get(monitor) / 3f));
		}

		/// <summary>
		/// 치료 전술 회복량 획득.
		/// </summary>
		/// <param name="sourceList"></param>
		/// <param name="targetCount"></param>
		/// <returns></returns>
		public static int GetHealPointWhenHealTactics(Manager.BaseManager manager, IEnumerable<Controller.BaseStat> sourceList, int targetCount)
		{
			var monitor = manager.Monitor;
			float a = 0;

			foreach (var source in sourceList)
				a += source.baseAtk.Get(monitor);

			return Mathf.FloorToInt(a * 0.125f / targetCount);
		}

		public static float GetSkillProbability(Manager.BaseManager manager, Controller.BaseController source)
		{
			if (source.SkillData == null)
				return 0f;

			//if (source.SkillData.skillType != 1)
			//	return 0f;

			if (manager.cheat.isSkillProbPlayer)
			{
				if (manager.DataList[ESide.PLAYER].CharacterList.Exists(data => data == source))
					return manager.cheat.skillProbPlayer;
			}

			if (manager.cheat.isSkillProbEnemy)
			{
				if (manager.DataList[ESide.ENEMY].CharacterList.Exists(data => data == source))
					return manager.cheat.skillProbEnemy;
			}

			switch (source.SkillData.rateType)
			{
				case 1:
					return source.SkillData.rate;

				case 2:
				case 3:
					return source.Stat.skillRate.Get(manager.Monitor);
			}

			return 0f;
		}

		/// <summary>
		/// 최초 대기시간 획득.
		/// </summary>
		/// <param name="source"></param>
		/// <returns></returns>
		public static float GetInitialActionTime(Manager.BaseManager manager, Controller.BaseStat source)
		{
			if (manager.cheat.isActionTimeInitial)
				return UnityEngine.Random.Range(manager.cheat.actionTimeInitialMin, manager.cheat.actionTimeInitialMax);

			var monitor = manager.Monitor;

			return Mathf.Clamp(50f / (source.init.Get(monitor) + source.atkInit.Get(monitor)), 0.2f, 6f) * UnityEngine.Random.Range(0.95f, 1.05f);	// 최초 공격 대기시간 = 50 / (우선권 + 공격 우선권), 최소=0.2, 최대=6.0
		}

		/// <summary>
		/// 대기시간 획득.
		/// </summary>
		/// <param name="source"></param>
		/// <returns></returns>
		public static float GetActionTime(Manager.BaseManager manager, Controller.BaseStat source)
		{
			if (manager.cheat.isActionTime)
				return UnityEngine.Random.Range(manager.cheat.actionTimeMin, manager.cheat.actionTimeMax);

			var monitor = manager.Monitor;

			// 공격 대기시간 감소 실효 값 = 2 / ( 1 + 20 ^ (( 공격대기시간 감소 )/500) )
			// 기본 대기시간 = 공격 대기시간 * (1 – 공격 대기시간 감소 실효 값), 최소=2.0, 최대=5.0
			float a = 2f / (1f + Mathf.Pow(20f, source.atkCooltimeMod.Get(monitor) / 500));
			float b = Mathf.Clamp(source.atkCooltime.Get(monitor) * (1f - a), 2f, 5f);

			// 실제 대기시간 = 기본 대기시간 * random(0.95~1.05)
			return b * UnityEngine.Random.Range(0.95f, 1.05f);
		}

		/// <summary>
		/// 공격시 전술 포인트 획득.
		/// </summary>
		/// <param name="source"></param>
		/// <returns></returns>
		public static float GetTacticsPointWhenAttack(Manager.BaseManager manager, Controller.BaseStat source)
		{
			return source.getFromAtk.Get(manager.Monitor);
		}

		/// <summary>
		/// 피격시 전술 포인트 획득.
		/// </summary>
		/// <param name="source"></param>
		/// <returns></returns>
		public static float GetTacticsPointWhenHit(Manager.BaseManager manager, Controller.BaseStat source)
		{
			return source.getFromHit.Get(manager.Monitor);
		}

		/// <summary>
		/// 유닛 기준 경험치 획득.
		/// </summary>
		/// <param name="levelAvgSide0"></param>
		/// <param name="levelAvgSide1"></param>
		/// <param name="isWin"></param>
		/// <returns></returns>
		public static float GetUnitBaseExp(float levelAvgSide0, float levelAvgSide1, bool isWin)
		{
			float corr = Mathf.Clamp(levelAvgSide1 / (isWin ? levelAvgSide0 : levelAvgSide0 * 3), 0.3f, 1.3f);

			return 50f * corr;
		}

		/// <summary>
		/// 길드 기준 경험치 획득.
		/// </summary>
		/// <param name="levelAvgSide0"></param>
		/// <param name="levelAvgSide1"></param>
		/// <param name="isWin"></param>
		/// <returns></returns>
		public static float GetLegionBaseExp(float levelAvgSide0, float levelAvgSide1, bool isWin)
		{
			float corr = Mathf.Clamp(levelAvgSide1 / (isWin ? levelAvgSide0 : levelAvgSide0 * 5), 0.3f, 1f);

			return 40f * corr;
		}
	}
}
